package org.dfl.core.model.analysis;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.dfl.messages.Messages;
import org.dfl.plugin.editor.model.DflDiagram;
import org.dfl.plugin.editor.model.PlaceModelElement;
import org.dfl.plugin.editor.model.TransitionModelElement;
import org.dfl.plugin.views.DflPluginConsole;

public class ReverseDiagram {

	private Map<String, RPlace> places = new HashMap<String, RPlace>();
	private Map<String, RTransition> transitions = new HashMap<String, RTransition>();
	private boolean initialized = false;
	private boolean newReduction;

	//private void msg(String s) {
	//	System.out.println(s);
	//}
	
	private boolean init(DflDiagram diagram) {
		places.clear();
		transitions.clear();
		for (PlaceModelElement pmp: diagram.getPlaces()) {
			RPlace p = new RPlace(pmp.getPlace());
			places.put(p.getId(), p);
		}
		
		for (TransitionModelElement tmt: diagram.getTransitions()) {
			RTransition t = new RTransition(tmt.getTransition(), this.places);
			transitions.put(t.getId(), t);
		}
		return true;
	}

	private void removePlace(String key) {
		if (places.containsKey(key)) {
			RPlace p = places.get(key);
			if (p != null) {
				p.disconnectAllInputEdges();
				p.disconnectAllOutputEdges();
				places.remove(key);
			}
		}
	}
	
	private void removeTransition(String key) {
		if (transitions.containsKey(key)){
			RTransition t = transitions.get(key);
			if (t != null) {
				t.disconnectAllInputEdges();
				t.disconnectAllOutputEdges();
				transitions.remove(key);
			}
		}
	}

	public ReverseDiagram(DflDiagram diagram) {
	  this.initialized = init(diagram);
	}
	
	public boolean isHierarchical() {
		if (this.initialized) {
			this.newReduction = true;
			while (this.newReduction) {
				this.newReduction = false;
				tryReductionA();
				tryReductionB();
				tryReductionC();
				tryReductionDE();
				tryReductionF();
				if ((places.size() == 2) && (transitions.size() == 1)) {
					DflPluginConsole.outputInfo(Messages.CheckHierarchicalCommand_NRCExpression + "\n");
					DflPluginConsole.outputMessage(transitions.values().iterator().next().getFunctionLabel() + "\n");
					return true;
				}
			}
		}
		return false;
	}
	
/*	private void tryReductionA() {
		Iterator<RPlace> iter1 = places.values().iterator();
		while (iter1.hasNext()) {
			RPlace p1 = iter1.next();
			REdge e1 = p1.getSingleOutputEdge();
			if ((e1 != null) && (e1.isBlank())) {
				RTransition t1 = e1.getTransition();
				REdge e2 = t1.getSingleOutputEdge();
				if ((e2 != null) && (e2.isBlank()) && (t1.getInputEdges().size() == 1)) {
					RPlace p2 = e2.getPlace();
					if ((p2.getInputEdges().size() == 1) && (p2.getOutputEdges().size() > 0)) {
						REdge eIn = p1.getSingleInputEdge();
						REdge eOut = p2.getSingleOutputEdge();
						Boolean outNested = false;
						for (REdge e3: p2.getOutputEdges()) {
							if (e3.isNested()) {
								outNested = true;
							}
						}
						if (((eOut != null) && (eOut.isNested()) && (eIn != null) && (eIn.isNested())) ||
								(!outNested))
						//msg("A: P(" + p2.getId() + "), T(" + t1.getId() + ")");
						compoundFunctionsA(t1, p2, e1);
						p1.reconnectOutputEdges(p2);
						removePlace(p2.getId());
						removeTransition(t1.getId());
						newReduction = true;
						iter1 = places.values().iterator();
					}
				}
			}
		}
	}
*/
	
	private void tryReductionA() {
		Iterator<RPlace> iter1 = places.values().iterator();
		while (iter1.hasNext()) {
			RPlace p1 = iter1.next();
			REdge e1 = p1.getSingleOutputEdge();
			if ((e1 != null) && (e1.isBlank())) {
				RTransition t1 = e1.getTransition();
				REdge e2 = t1.getSingleOutputEdge();
				if ((e2 != null) && (e2.isBlank()) && (t1.getInputEdges().size() == 1)) {
					RPlace p2 = e2.getPlace();
					if ((p2.getInputEdges().size() == 1) && (p2.getOutputEdges().size() > 0)) {
						//msg("A: P(" + p2.getId() + "), T(" + t1.getId() + ")");
						compoundFunctionsA(t1, p2, e1);
						p1.reconnectOutputEdges(p2);
						removePlace(p2.getId());
						removeTransition(t1.getId());
						newReduction = true;
						iter1 = places.values().iterator();
					}
				}
			}
		}
	}
	
	private void tryReductionB() {
		Iterator<RTransition> iter1 = transitions.values().iterator();
		while (iter1.hasNext()) {
			RTransition t1 = iter1.next();
			REdge e1 = t1.getSingleOutputEdge();
			if ((e1 != null) && (e1.isBlank())) {
				RPlace p1 = e1.getPlace();
				REdge e2 = p1.getSingleOutputEdge();
				if ((e2 != null) && (e2.isBlank()) && (p1.getInputEdges().size() == 1)) {
					RTransition t2 = e2.getTransition();
					if (t2.getInputEdges().size() == 1) {
						//msg("B: P(" + p1.getId() + "), T(" + t2.getId() + ")");
						compoundFunctionsB(t1, t2, e2);
						t1.reconnectOutputEdges(t2);
						removeTransition(t2.getId());
						removePlace(p1.getId());
						newReduction = true;
						iter1 = transitions.values().iterator();
					}
				}
			}
		}
	}
	
	private	void tryReductionC() {
		Iterator<RTransition> iter1 = transitions.values().iterator();
		while (iter1.hasNext()) {
			RTransition t1 = iter1.next();
			if (t1.getOutputEdges().size() == 2) {
				REdge e1 = t1.getFirstOutputEdge();
				REdge e2 = t1.getNextOutputEdge();
				if ((e1 != null) && (e2 != null) && (e1.isNested() != e2.isNested()) && (!e1.isAnnotated()) && (!e2.isAnnotated())) {
					RPlace p1 = e1.getPlace();
					RPlace p2 = e2.getPlace();
					REdge e3 = p1.getSingleOutputEdge();
					REdge e4 = p2.getSingleOutputEdge();
					if ((e3 != null) && (e4 != null) && (p1.getInputEdges().size() == 1) && (p2.getInputEdges().size() == 1)) {
						RTransition t2 = e3.getTransition();
						if ((t2.equals(e4.getTransition())) && (t2.getInputEdges().size() == 2) 
								&& (e1.isNested() == e3.isNested()) && (e2.isNested() == e4.isNested())
								&& (!e3.isAnnotated()) && (!e4.isAnnotated())) {
							//msg("C: P(" + p1.getId() + "), P(" + p2.getId() + "), T(" + t2.getId() + ")");
							compoundFunctionsC(t1, t2, e3, e4);
							t1.reconnectOutputEdges(t2);
							removeTransition(t2.getId());
							removePlace(p1.getId());
							removePlace(p2.getId());
							newReduction = true;
							iter1 = transitions.values().iterator();
						}
					}
				}
			}
		}
	}
	
	private void tryReductionDE() {
		Iterator<RPlace> iter1 = places.values().iterator();
		while (iter1.hasNext()) {
			RPlace p1 = iter1.next();
			if (p1.getOutputEdges().size() > 1) {
				boolean found = false;
				Iterator<REdge> iter2 = p1.getOutputEdges().iterator();
				while ((!found) && (iter2.hasNext())) {
					REdge e1 = iter2.next();
					if (e1.isAnnotated()) {
						REdge e2 = p1.findMatchingOutputEdge(e1);
						if (e2 != null) {
							found = true;
							//msg("DE: T(" + e2.getTransition().getId() + ")");
							compoundFunctionsDE(e1, e2);
							if (e2.getFlag() < REdge.E_BLANK) {
								removeTransition(e2.getTransition().getId());
								e1.setFlag(REdge.E_BLANK);
							} else {
								removeTransition(e1.getTransition().getId());
								e2.setFlag(REdge.E_BLANK);
							}
							newReduction = true;
							iter1 = places.values().iterator();							
						}
					}
				}
			}
		}
	}
						
	private void tryReductionF() {
		Iterator<RPlace> iter1 = places.values().iterator();
		while (iter1.hasNext()) {
			RPlace p1 = iter1.next();
			if (p1.getOutputEdges().size() > 0) {
				RPlace p2 = p1.findMatchingPlace();
				if (p2 != null) {
					//msg("F: P(" + p2.getId() + ")");
					compoundFunctionsF(p1, p2);
					removePlace(p2.getId());
					newReduction = true;
					iter1 = places.values().iterator();					
				}
			}
		}
	}

	private void compoundFunctionsA(RTransition src, RPlace p, REdge eIn) {
		for (REdge e: p.getOutputEdges()) {
			RTransition dst = e.getTransition();
			String var = dst.getVar(e.getId());
			dst.removeVar(e.getId());
			dst.copyVar(src, e.getId());
			if (!e.isNested()) {
				dst.setFunctionLabel(replaceWithIndent(dst.getFunctionLabel(), var, src.getFunctionLabel()));
			} else {
				String newVar = VariableNameFactory.getUVN("r");
				String s1 = src.getVar(eIn.getId());
				String s2 = dst.getFunctionLabel();
				String s3 = src.getFunctionLabel().replace(s1, newVar);
				s1 = indentString(s1, 1);
				s2 = indentString(s2, 1);
				s3 = indentString(s3, 1);
				dst.setFunctionLabel(replaceWithIndent(s2, var, "for " + newVar + " in\n" + s1 + "\nreturn\n" + s3));
			}
			//msg(dst.getFunctionLabel());
		}
	}
	
	private void compoundFunctionsB(RTransition src, RTransition dst, REdge e) {
		String var = dst.getVar(e.getId());
		dst.removeVar(e.getId());
		src.setFunctionLabel(replaceWithIndent(dst.getFunctionLabel(), var, src.getFunctionLabel()));
		//msg(src.getFunctionLabel());
	}
	
	private void compoundFunctionsC(RTransition src, RTransition dst, REdge e1, REdge e2) {
		REdge eNested = (e1.isNested())?e1:e2;
		REdge eNotNested = (e1.isNested())?e2:e1;
		String newVar = VariableNameFactory.getUVN("t");
		
		dst.setFunctionLabel(replaceWithIndent(dst.getFunctionLabel(),
				dst.getVar(eNotNested.getId()), newVar));
		dst.setFunctionLabel(replaceWithIndent(dst.getFunctionLabel(),
				dst.getVar(eNested.getId()), newVar));
		
		String s1 = src.getFunctionLabel();
		String s2 = dst.getFunctionLabel();
		s1 = indentString(s1, 1);
		s2 = indentString(s2, 1);
		src.setFunctionLabel("let " + newVar + " =\n" + s1 + "\nin\n" + s2);		
		//msg(src.getFunctionLabel());
	}

	private void compoundFunctionsDE(REdge srcE1, REdge srcE2) {
		REdge eTrue = (srcE1.getFlag() > REdge.E_BLANK)?srcE1:srcE2;
		REdge eFalse = (srcE1.getFlag() > REdge.E_BLANK)?srcE2:srcE1;
		RTransition tTrue = eTrue.getTransition();
		RTransition tFalse = eFalse.getTransition();
		for (REdge e1: tTrue.getInputEdges()) {
			RPlace p = e1.getPlace();
			boolean found = false;
			Iterator<REdge> iter = p.getOutputEdges().iterator();
			while ((!found) && (iter.hasNext())) {
				REdge e2 = iter.next();
				if (e2.getTransition().equals(tFalse)) {
					found = true;
					tFalse.setFunctionLabel(replaceWithIndent(tFalse.getFunctionLabel(),
							tFalse.getVar(e2.getId()), tTrue.getVar(e1.getId())));
				}
			}
		}
		String cond = tTrue.getVar(eTrue.getId());
		if (eTrue.getFlag() == REdge.E_EMPTY) {
			cond += " is empty";
		}
		String s1 = tTrue.getFunctionLabel();
		String s2 = tFalse.getFunctionLabel();
		s1 = indentString(s1, 1);
		s2 = indentString(s2, 1);
		tTrue.setFunctionLabel("if\n" + indentString(cond, 1) + "\nthen\n" + s1 + "\nelse\n" + s2);		
		//msg(tTrue.getFunctionLabel());
	}
	
	private void compoundFunctionsF(RPlace p1, RPlace p2) {
		for (REdge e1: p1.getOutputEdges()) {
			RTransition dst = e1.getTransition();
			boolean found = false;
			Iterator<REdge> iter = p2.getOutputEdges().iterator();
			while ((!found) && (iter.hasNext())) {
				REdge e2 = iter.next();
				if (e2.getTransition().equals(dst)) {
					found = true;
					dst.setFunctionLabel(replaceWithIndent(dst.getFunctionLabel(),
							dst.getVar(e2.getId()), dst.getVar(e1.getId())));
					dst.removeVar(e2.getId());
				}
			}
			//msg(dst.getFunctionLabel());
		}
	}
	
	private int leadingTabs(String s) {
		int ret = 0;
		while ((s.length() > ret) && (s.charAt(ret) == ' ')) {
			ret++;
		}
		return ret / 3;
	}
	
	private int leadingTabs(String s, int index) {
		while ((index > 0) && (s.charAt(index) != '\n')) {
			index--;
		}
		if (index == 0) {
			return leadingTabs(s);
		} else {
			return leadingTabs(s.substring(index + 1));
		}
	}
	
	private String indentString(String s, int tabCount) {
		return indentString(s, tabCount, true);
	}
	
	private String indentString(String s, int tabCount, boolean indentFirstLine) {
		String indent = "";
		int i;
		for (i = 0; i < tabCount; i++) {
			indent += "   ";
		}
		if (indentFirstLine) {
			s = indent + s;
		}
		i = s.indexOf('\n');
		while (i != -1) {
			s = s.substring(0, i + 1) + indent + s.substring(i + 1);
			i = s.indexOf('\n', i + 1 + tabCount);	
		}
		return s;
	}

	private String replaceWithIndent(String s, String search, String replacement) {
		if (replacement.indexOf('\n') != -1) {
			int i = s.indexOf(search);
			while (i != -1) {
				int tCount = leadingTabs(s, i);
				String rep = "";
				boolean firstElem = true;
				int n = i - 1;
				while ((firstElem) && (n > 0) && (s.charAt(n) != '\n')) {
					if (s.charAt(n) != ' ') {
						firstElem = false;
					}
					n--;
				}
				if (firstElem) {
					rep = indentString(replacement, tCount, false);
					s = s.substring(0, i) + rep + s.substring(i + search.length());
				} else {
					rep = indentString(replacement, tCount + 1);
					s = s.substring(0, i) + "\n" + rep + s.substring(i + search.length());
				}
				i = s.indexOf(search, i + rep.length());
			}
		} else {
			s = s.replace(search, replacement);
		}
		return s;
	}
}
